-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Cartesian subplot updates using data joins #946
Conversation
- which handles fullLayout._plots creation and reference updates - rm block in Plots.transition that used to handle that
- we must now call drawAxes AFTER drawData (i.e. after updateSubplots) as drawAxes looks for some <g> that updateSubplots creates - move layoutStyles / Fx.init out of makePlotFramework - make makePlotFramework return 'FRAMEWORK' so that restyle polar <-> cartesian still works
This PR is currently in progress. Some image test mocks and a few Most of these issues are - I believe - caused by To note also, subroutine |
- so that these ref on fullLayout exist even for non-cartesian trace types.
- after several failed attempts at getting cartesian subplot creation/update/removal into the Cartesian.plot (like the other base plot module), I decide to go with a less ambitious refactoring where a drawFramework update step is added to the main Plotly.plot code path - Main reason: several layout components require the cartesian framework to be present in order to work properly.
- this suite used to rely on className 'bg' to grab the legend background element, but now className 'bg' is also used in <g subplot>.
- doCalcdata is now called before drawFramwework, so the ordering for which _ keys are added to the gd differs slightly.
subplotLayers.enter().append('g') | ||
.classed('subplot', true); | ||
|
||
subplotLayers.order(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See http://codepen.io/etpinard/pen/mAEBVz for example of this pattern
In commit 937c54b I settled for a less ambitious refactoring attempt in this PR. It turns out that (parts of) the cartesian framework are required in order to make (some) layout components draw properly. Meaning that we must draw the cartesian framework before getting to the main So, I decided to a In a future PR(s), we should spent some time refactoring |
@@ -54,7 +54,7 @@ describe('Test Plots', function() { | |||
expect(gd._fullData[1].z).toBe(newData[1].z); | |||
expect(gd._fullData[1]._empties).toBe(oldFullData[1]._empties); | |||
expect(gd._fullLayout.scene._scene).toBe(oldFullLayout.scene._scene); | |||
expect(gd._fullLayout._plots).toBe(oldFullLayout._plots); | |||
expect(gd._fullLayout._plots.plot).toBe(oldFullLayout._plots.plot); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_plots
is no longer relinked in Plots.supplyDefaults
as
newFullLayout._plots = oldFullLayout._plots
but as
var oldSubplots = oldFullLayout._plots || {},
newSubplots = newFullLayout._plots = {};
var ids = Plotly.Axes.getSubplots(mockGd);
for(var i = 0; i < ids.length; i++) {
var id = ids[i],
oldSubplot = oldSubplots[id],
plotinfo;
if(oldSubplot) {
plotinfo = newSubplots[id] = oldSubplot;
}
else {
plotinfo = newSubplots[id] = {};
plotinfo.id = id;
}
plotinfo.x = getAxisFunc(id, 'x');
plotinfo.y = getAxisFunc(id, 'y');
plotinfo.xaxis = plotinfo.x();
plotinfo.yaxis = plotinfo.y();
}
cc @rreusser
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So it looks like it reconstructs a new _plots
object with the same contents vs. just transferring it? Is that what makes the join work correctly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it looks like it reconstructs a new _plots object with the same contents vs. just transferring it?
Correct. Thanks for the headsup. Maybe we could do better.
Is that what makes the join work correctly?
I don't think reconstructing _plots
is necessary to make the joins work correctly.
|
||
Plotly.addTraces(gd, { x: [1, 2, 3], y: [4, 3, 2], name: 'Test2' }).then(function() { | ||
legend = document.getElementsByClassName('legend')[0]; | ||
bg = document.getElementsByClassName('bg')[0]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The subplot bg layer now also has class bg
, which made this query grab the wrong node.
// the new layout gets ignored (as it should) | ||
// but if there's no data there yet, it's just a placeholder... | ||
// then it should destroy and remake the plot | ||
if(hasData) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@alexcjohnson can you review these few lines?
Can you think of any other (edge) cases that I could have missed?
[] : | ||
[marginPushers, subroutines.layoutStyles]; | ||
|
||
return Lib.syncOrAsync(seq, gd); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no need to call Fx.init
here too as it is called in drawFramework
on every Plotly.plot
.
Previously, we need a change of subplot in order to trigger Fx.init
.
} | ||
|
||
|
||
function joinLayer(parent, nodeType, className) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good enough for Lib
function ???
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, def. We use this pattern all over the place, and it's invalid in d3 v4 without some minor modifications, so would be good to extract.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll defer that to a future PR.
@@ -559,6 +562,41 @@ function relinkPrivateKeys(toContainer, fromContainer) { | |||
} | |||
} | |||
|
|||
plots.linkSubplots = function(newFullData, newFullLayout, oldFullData, oldFullLayout) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This routine here should make fullLayout._plots[' '].x()
and fullLayout._plots[' '].y()
method obsolete.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cc @rreusser
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done in 2b05978
} | ||
|
||
plotinfo.x = getAxisFunc(id, 'x'); | ||
plotinfo.y = getAxisFunc(id, 'y'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here they are for backward-compatibility.
It might be worth the time to remove all x()
and y()
and replace them with _plots[''].xaxis
and _plots[''].yaxis
respectively.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I meant here. done in 2b05978
2b05978
to
ae3ab55
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see anything blocking here. I don't understand every bit of it since I haven't gotten too deep into the plot framework, but I like the change, so if it passes the tests, then I say 💃🏻
var xa = plotinfo.x(), | ||
ya = plotinfo.y(); | ||
|
||
var xa = plotinfo.xaxis, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is plotinfo.x()
removed here or in general? I had some subtle problems with one or the other being up to date as axis references change.
} | ||
else if(!hasSameSubplots) { | ||
// polar need a different framework | ||
if(gd.framework !== makePlotFramework) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The naming is a bit confusing to me since framework = noun while makePlotFramework = verb, but this is probably nothing new and not a big deal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree this is very strange. But, like pointed out by the comment above, the only reason this line is here is because of polar.
As polar is so broken at the moment, I prefer not changing things too much until we fully refactor it.
.style('fill', 'none') | ||
.classed('crisp', true); | ||
}); | ||
return 'FRAMEWORK'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does this return code do? It seems 10% strange, so maybe a comment would be good if it's actually needed/expected.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh good catch.
There's no need for this line anymore.
@@ -1369,8 +1373,8 @@ axes.doTicks = function(gd, axid, skipTitle) { | |||
if(axid === 'redraw') { | |||
fullLayout._paper.selectAll('g.subplot').each(function(subplot) { | |||
var plotinfo = fullLayout._plots[subplot], | |||
xa = plotinfo.x(), | |||
ya = plotinfo.y(); | |||
xa = plotinfo.xaxis, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Big fan of this change! 🍻
} | ||
|
||
|
||
function joinLayer(parent, nodeType, className) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, def. We use this pattern all over the place, and it's invalid in d3 v4 without some minor modifications, so would be good to extract.
@@ -54,7 +54,7 @@ describe('Test Plots', function() { | |||
expect(gd._fullData[1].z).toBe(newData[1].z); | |||
expect(gd._fullData[1]._empties).toBe(oldFullData[1]._empties); | |||
expect(gd._fullLayout.scene._scene).toBe(oldFullLayout.scene._scene); | |||
expect(gd._fullLayout._plots).toBe(oldFullLayout._plots); | |||
expect(gd._fullLayout._plots.plot).toBe(oldFullLayout._plots.plot); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So it looks like it reconstructs a new _plots
object with the same contents vs. just transferring it? Is that what makes the join work correctly?
- to allow calling outside the drawFramework step
None of the changes or comments were blocking, so gets the 💃🏻 from me (once you're happy)! |
return subplotData; | ||
} | ||
|
||
function makeSubplotLayer(plotinfo) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Important the subplot layer join step is free of references to gd._fullLayout
.
This means that other components (e.g. the range slider 😏 ) can call it (with sufficiently mocked plotinfo
object with xaxis
, yaxis
, _id
and plotgroup
) and get the subplot layers appended to plotinfo.plotgroup
in return !
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hooray for clever code reuse! As long as no too clever. 🎉
Merge looks great, but I did have a question about this: how does it affect non-cartesian plots? Or does it affect non-cartesian plots? Specifically, does this add or modify the considerations involved in adding object constancy to pie or bar charts? (though bar is cartesian, right? I haven't worked with them much beyond making sure I don't break them) |
It should not affect non-cartesian whatsoever. After this PR, all cartesian subplots are inserted in All per-subplot routines are now called inside |
- bug noticed when both overlaying x and y axes have `showline: true`. - ... but `Plotly.restyle(gd, 'visible', true, [1])` does not work for the added mock - both bugs have been broken since PR #946 released in 1.18.0
resolves #634
In trying to make the cartesian plot routine a little more reusable for range slider 2.0, I ran into these
which basically destroys and redraw all axes every time the subplot layout changes. We could do better.
In this PR, I attempt to move the subplot creation / updates out of the
makePlotFramework
and into theCartesian
module using a d3 data join. In doing so,fullLayout._plots
logic is cleaned up considerably (resolving issue #634).cc @rreusser @alexcjohnson